上一篇進行 Code Review 時,發現 loadItems() 雖然標示會回傳 CollectionEntry[],卻沒有真的檢查 JSON.parse() 還原後的資料格式。
既然已經找到問題,這次就把它整理成比較明確的需求,再交給 AI:
請修改目前的
loadItems(),在JSON.parse()後確認資料是陣列,並檢查每一筆資料是否符合CollectionEntry的格式。如果資料格式不正確,請回傳空陣列。不要安裝新的套件,也不要修改其他功能。完成後請說明修改了哪些程式碼及原因。
和一開始只說「幫我做一個收藏清單」相比,這次提出的範圍小了很多,也直接指定要修改的問題與不能更動的內容。
這裡新增一個函式,檢查單筆資料是否符合 CollectionEntry:
function isCollectionEntry(
value: unknown,
): value is CollectionEntry {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof value.id === 'string' &&
'title' in value &&
typeof value.title === 'string' &&
'type' in value &&
(value.type === 'manga' || value.type === 'anime') &&
'status' in value &&
(
value.status === 'want' ||
value.status === 'watching' ||
value.status === 'completed'
)
)
}
雖然只是要檢查資料,但還是突然多了不少東西。
仔細往下看,可以發現它其實是在逐一確認:
null
id 和 title 是否存在,而且是字串type 是否為 manga 或 anime
status 是否為專案允許的其中一種狀態這些條件都成立時,才會回傳 true。
unknown 的原因函式接收的參數被標示為:
value: unknown
unknown 可以先理解成:
現在還不知道這份資料是什麼,所以使用前必須先檢查。
因為從 localStorage 讀出並經過 JSON.parse() 的內容,不一定符合專案預期,所以不能一開始就直接把它當成 CollectionEntry。
使用 unknown 後,TypeScript 會要求程式先透過 typeof、null 判斷與屬性檢查,逐步確認這份資料能不能使用。
value is CollectionEntry 的意思函式回傳型別寫成:
value is CollectionEntry
這種寫法稱為 Type Predicate,可以先理解成:
如果這個函式回傳
true,TypeScript 就可以把value當成CollectionEntry。
它不只是告訴 TypeScript「相信我」,而是搭配函式裡實際執行的條件判斷,檢查資料的內容。
TypeScript 官方文件:使用 Type Predicate 縮小型別
loadItems()有了 isCollectionEntry(),loadItems() 就可以改成:
export function loadItems(): CollectionEntry[] {
try {
const savedItems = localStorage.getItem(STORAGE_KEY)
if (!savedItems) return []
const parsedItems: unknown = JSON.parse(savedItems)
return Array.isArray(parsedItems) &&
parsedItems.every(isCollectionEntry)
? parsedItems
: []
} catch {
return []
}
}
這次不會在 JSON.parse() 後直接回傳資料,而是先確認:
Array.isArray(parsedItems)
檢查最外層是不是陣列,再使用:
parsedItems.every(isCollectionEntry)
逐一檢查陣列中的每筆資料。
every() 代表每一筆資料都要通過 isCollectionEntry()。只要其中一筆不符合格式,最後就會回傳空陣列。
修改前的流程是:
讀取資料
↓
JSON.parse()
↓
直接當成 CollectionEntry[] 使用
修改後則多了一層確認:
讀取資料
↓
JSON.parse()
↓
確認是不是陣列
↓
確認每筆資料的格式
↓
符合才當成 CollectionEntry[] 使用
原本只是透過 TypeScript 標示預期的型別,現在則真的在程式執行時檢查資料。
這次已經把問題、修改範圍和預期結果寫得很清楚,但 AI 交出程式碼後,還是不能只看到內容變多就直接接受。
不再只把一句模糊的「幫我修好」交給 AI,而是先根據自己找到的問題,把修改範圍說清楚。
後續也可以視需求,往不同方向確認:
type 和 status 的值是否符合專案定義unknown 表示目前還不能確定資料的型別isCollectionEntry() 會逐一檢查資料需要的欄位value is CollectionEntry 表示通過檢查後,可以將資料當成 CollectionEntry
Array.isArray() 檢查最外層是否為陣列every() 確認陣列中的每一筆資料都符合格式以前交給 AI 的需求比較像是在說自己想要什麼功能,這次則是先找出問題,再限制修改範圍,最後重新理解它給出的解法。雖然修改也交給了 AI,但每當經過修改,是否要直接接受並使用,仍舊不能單純只看它有沒有成功產生程式碼。
下篇見:)